package byx.container.annotation.extension.impl;

import byx.container.annotation.annotation.Create;
import byx.container.annotation.annotation.Id;
import byx.container.annotation.annotation.Register;
import byx.container.annotation.exception.*;
import byx.container.annotation.extension.core.AnnotationMetadata;
import byx.container.annotation.extension.core.ClassPostProcessorContext;
import byx.container.annotation.util.OrderUtils;
import byx.container.core.Container;
import byx.container.core.ObjectFactories;
import byx.container.core.ObjectFactory;
import byx.container.core.RegisterInfo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static byx.container.core.ObjectFactories.fromContainer;

/**
 * 根据类的定义生成相应的ObjectFactory
 */
public class ClassScanner {
    private final Container container;
    private final ParameterResolver parameterResolver;

    public ClassScanner(Container container,
                        ParameterResolver parameterResolver) {
        this.container = container;
        this.parameterResolver = parameterResolver;
    }

    private Constructor<?> findConstructor(Class<?> type) {
        Constructor<?>[] constructors = type.getConstructors();

        if (constructors.length == 0) {
            throw new ConstructorNotFoundException(type);
        }
        if (constructors.length == 1) {
            return constructors[0];
        }

        constructors = Arrays.stream(constructors)
                .filter(c -> c.isAnnotationPresent(Create.class))
                .toArray(Constructor<?>[]::new);

        if (constructors.length == 0) {
            throw new MultiConstructorException(type);
        }
        if (constructors.length > 1) {
            throw new MultiCreateFlagException(type);
        }

        return constructors[0];
    }

    private String getTypeId(Class<?> type) {
        if (!type.isAnnotationPresent(Id.class)) {
            return type.getCanonicalName();
        }
        String id = type.getAnnotation(Id.class).value();
        return "".equals(id) ? type.getCanonicalName() : id;
    }

    private String getMethodId(Method method) {
        if (!method.isAnnotationPresent(Id.class)) {
            return method.getName();
        }
        String id = method.getAnnotation(Id.class).value();
        return "".equals(id) ? method.getName() : id;
    }

    private ObjectFactory<?> getConstructorFactory(Class<?> type, Constructor<?> constructor) {
        ObjectFactory<?>[] params = parameterResolver.getParamFactories(constructor);
        return ObjectFactories.ctor(type, params);
    }

    private ObjectFactory<?> getMethodFactory(Method method) {
        ObjectFactory<?>[] params = parameterResolver.getParamFactories(method);
        Class<?> instanceType = method.getDeclaringClass();
        String instanceId = getTypeId(instanceType);
        return fromContainer(container, instanceId).call(method.getName(), params);
    }

    public ClassPostProcessorContext getTypeContext(Class<?> type) {
        if (type.isInterface()) {
            throw new InterfaceInstantiateException(type);
        }

        if (Modifier.isAbstract(type.getModifiers())) {
            throw new AbstractClassInstantiateException(type);
        }

        Constructor<?> constructor = findConstructor(type);
        ObjectFactory<?> factory = getConstructorFactory(type, constructor);
        String id = getTypeId(type);
        int order = OrderUtils.getOrder(type);
        AnnotationMetadata annotationMetadata = new AnnotationMetadata(type.getAnnotations());
        RegisterInfo registerInfo = new RegisterInfo(id, type, order, factory);
        return new ClassPostProcessorContext(registerInfo, container, annotationMetadata);
    }

    public ClassPostProcessorContext getMethodContext(Method method) {
        Class<?> type = method.getReturnType();
        ObjectFactory<?> methodFactory = getMethodFactory(method);
        String id = getMethodId(method);
        int order = OrderUtils.getOrder(method);
        AnnotationMetadata annotationMetadata = new AnnotationMetadata(method.getAnnotations());
        RegisterInfo registerInfo = new RegisterInfo(id, type, order, methodFactory);
        return new ClassPostProcessorContext(registerInfo, container, annotationMetadata);
    }

    public List<ClassPostProcessorContext> getTypeAndMethodContexts(Class<?> type) {
        List<ClassPostProcessorContext> contexts = new ArrayList<>();
        ClassPostProcessorContext ctx = getTypeContext(type);
        contexts.add(ctx);
        Arrays.stream(type.getMethods())
                .filter(m -> m.isAnnotationPresent(Register.class))
                .forEach(m -> contexts.add(getMethodContext(m)));
        return contexts;
    }
}
